package org.apache.lucene.search;

/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.IOException;

import java.util.HashSet;

import java.util.Set;

import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;

/** The abstract base class for queries.
    <p>Instantiable subclasses are:
    <ul>
    <li> {@link TermQuery}
    <li> {@link MultiTermQuery}
    <li> {@link BooleanQuery}
    <li> {@link WildcardQuery}
    <li> {@link PhraseQuery}
    <li> {@link PrefixQuery}
    <li> {@link MultiPhraseQuery}
    <li> {@link FuzzyQuery}
    <li> {@link TermRangeQuery}
    <li> {@link NumericRangeQuery}
    <li> {@link org.apache.lucene.search.spans.SpanQuery}
    </ul>
    <p>A parser for queries is contained in:
    <ul>
    <li>{@link org.apache.lucene.queryParser.QueryParser QueryParser}
    </ul>
*/
public abstract class Query implements java.io.Serializable, Cloneable {
    private float boost = 1.0f; // query boost factor

    /** Sets the boost for this query clause to <code>b</code>.  Documents
     * matching this clause will (in addition to the normal weightings) have
     * their score multiplied by <code>b</code>.
     */
    public void setBoost(float b) {
        boost = b;
    }

    /** Gets the boost for this clause.  Documents matching
     * this clause will (in addition to the normal weightings) have their score
     * multiplied by <code>b</code>.   The boost is 1.0 by default.
     */
    public float getBoost() {
        return boost;
    }

    /** Prints a query to a string, with <code>field</code> assumed to be the 
     * default field and omitted.
     * <p>The representation used is one that is supposed to be readable
     * by {@link org.apache.lucene.queryParser.QueryParser QueryParser}. However,
     * there are the following limitations:
     * <ul>
     *  <li>If the query was created by the parser, the printed
     *  representation may not be exactly what was parsed. For example,
     *  characters that need to be escaped will be represented without
     *  the required backslash.</li>
     * <li>Some of the more complicated queries (e.g. span queries)
     *  don't have a representation that can be parsed by QueryParser.</li>
     * </ul>
     */
    public abstract String toString(String field);

    /** Prints a query to a string. */
    @Override
    public String toString() {
        return toString("");
    }

    /**
     * Expert: Constructs an appropriate Weight implementation for this query.
     * 
     * <p>
     * Only implemented by primitive queries, which re-write to themselves.
     */
    public Weight createWeight(Searcher searcher) throws IOException {
        throw new UnsupportedOperationException("Query " + this + " does not implement createWeight");
    }

    /**
     * Expert: Constructs and initializes a Weight for a <b>top-level</b> query.
     * @deprecated never ever use this method in {@link Weight} implementations.
     * Subclasses of {@code Query} should use {@link #createWeight}, instead.
     */
    @Deprecated
    public final Weight weight(Searcher searcher) throws IOException {
        return searcher.createNormalizedWeight(this);
    }

    /** Expert: called to re-write queries into primitive queries. For example,
     * a PrefixQuery will be rewritten into a BooleanQuery that consists
     * of TermQuerys.
     */
    public Query rewrite(IndexReader reader) throws IOException {
        return this;
    }

    /** Expert: called when re-writing queries under MultiSearcher.
     *
     * Create a single query suitable for use by all subsearchers (in 1-1
     * correspondence with queries). This is an optimization of the OR of
     * all queries. We handle the common optimization cases of equal
     * queries and overlapping clauses of boolean OR queries (as generated
     * by MultiTermQuery.rewrite()).
     * Be careful overriding this method as queries[0] determines which
     * method will be called and is not necessarily of the same type as
     * the other queries.
    */
    public Query combine(Query[] queries) {
        HashSet<Query> uniques = new HashSet<Query>();
        for (int i = 0; i < queries.length; i++) {
            Query query = queries[i];
            BooleanClause[] clauses = null;
            // check if we can split the query into clauses
            boolean splittable = (query instanceof BooleanQuery);
            if (splittable) {
                BooleanQuery bq = (BooleanQuery) query;
                splittable = bq.isCoordDisabled();
                clauses = bq.getClauses();
                for (int j = 0; splittable && j < clauses.length; j++) {
                    splittable = (clauses[j].getOccur() == BooleanClause.Occur.SHOULD);
                }
            }
            if (splittable) {
                for (int j = 0; j < clauses.length; j++) {
                    uniques.add(clauses[j].getQuery());
                }
            } else {
                uniques.add(query);
            }
        }
        // optimization: if we have just one query, just return it
        if (uniques.size() == 1) {
            return uniques.iterator().next();
        }
        BooleanQuery result = new BooleanQuery(true);
        for (final Query query : uniques)
            result.add(query, BooleanClause.Occur.SHOULD);
        return result;
    }

    /**
     * Expert: adds all terms occurring in this query to the terms set. Only
     * works if this query is in its {@link #rewrite rewritten} form.
     * 
     * @throws UnsupportedOperationException if this query is not yet rewritten
     */
    public void extractTerms(Set<Term> terms) {
        // needs to be implemented by query subclasses
        throw new UnsupportedOperationException();
    }

    /** Expert: merges the clauses of a set of BooleanQuery's into a single
     * BooleanQuery.
     *
     *<p>A utility for use by {@link #combine(Query[])} implementations.
     */
    public static Query mergeBooleanQueries(BooleanQuery... queries) {
        HashSet<BooleanClause> allClauses = new HashSet<BooleanClause>();
        for (BooleanQuery booleanQuery : queries) {
            for (BooleanClause clause : booleanQuery) {
                allClauses.add(clause);
            }
        }

        boolean coordDisabled = queries.length == 0 ? false : queries[0].isCoordDisabled();
        BooleanQuery result = new BooleanQuery(coordDisabled);
        for (BooleanClause clause2 : allClauses) {
            result.add(clause2);
        }
        return result;
    }

    /** Expert: Returns the Similarity implementation to be used for this query.
     * Subclasses may override this method to specify their own Similarity
     * implementation, perhaps one that delegates through that of the Searcher.
     * By default the Searcher's Similarity implementation is returned.
     * @deprecated Instead of using "runtime" subclassing/delegation, subclass the Weight instead.
     */
    @Deprecated
    public Similarity getSimilarity(Searcher searcher) {
        return searcher.getSimilarity();
    }

    /** Returns a clone of this query. */
    @Override
    public Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException("Clone not supported: " + e.getMessage());
        }
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + Float.floatToIntBits(boost);
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Query other = (Query) obj;
        if (Float.floatToIntBits(boost) != Float.floatToIntBits(other.boost))
            return false;
        return true;
    }
}
